package uml;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import uml.internal.Association;
import uml.internal.Association.AssociationEnd;
import uml.internal.Generalisation;

import components.Argument;
import components.Class;
import components.ClassDiagram;
import components.Field;
import components.Method;
import components.Package;

public class XMIVisitor implements Visitor {
	//- PUBLIC & INTERNAL VARIABLES ---------------------------------------------------------------------------
	static final String GENERALIZATIONS = "_GENERALIZATIONS_";
	static final String PACKAGES = "_PACKAGES_";
	static final String ASSOCIATIONS = "_ASSOCIATIONS_";
	
	//- PRIVATE & PROTECTED VARIABLES -----------------------------------------------------------------------
	private final XMIOutputter out;
	
	// ids
	private int classId = 0;
	private int fieldId = 0;
	private int operationId = 0;
	private int parameterId = 0;
	private int genId = 0;
	private int assocId = 0;
	private int assocEndId;
	
	private boolean isReturn = true;
	
	private String packageName;
	private String className;
	
	private final List<Generalisation> gens = new ArrayList<Generalisation>();
	private final List<Association> assocs = new ArrayList<Association>();
	private Collection<Package> packages;
	
	//- CONSTRUCTOR ----------------------------------------------------------------------------------------------

	public XMIVisitor(XMIOutputter out) {
		this.out = out;
	}

	//- PUBLIC & INTERNAL METHODS -----------------------------------------------------------------------------
	@Override
	public void visit(Class c) {
		classId++;
		className = c.getName();

		out.openClass(c, classId, packageName);
		
		// pola
		for (Field f : c.getFields().values()) {
			visit(f);
		}
		
		// metody
		for (Method m : c.getMethods().values()){
			visit(m);
		}
		
		out.closeClassFeatures();
		
		// inner classes
		handleInnerClasses(c);
		
		// generalisation
		handleGeneralization(c);

		// interfaces
		handleInterfaces(c);
		
		out.closeClass(c);
		
		if(c.isEnum()){
			out.openCloseStereoptype(c.getName()+"-enumeration", "enumeration", c.getName());
		}
	}

	@Override
	public void visit(Argument a) {
		parameterId++;
		
		out.openCloseArgument(a, parameterId, operationId, classId, isReturn == true ? "return" : "in" );
	}

	@Override
	public void visit(Field f) {
		fieldId++;
		
		if(isSuchType(f.getType())){
			// dodaje asocjacje
			assocId++;
			Association a = new Association(assocId, f.getName(), packageName);
			boolean navigable = (className.equals(f.getType())) ? true : false ;
			a.setBegin(new AssociationEnd(assocEndId, true, assocId, f.getType()));
			assocEndId++;
			
			a.setEnd(new AssociationEnd(assocEndId, navigable, assocId, className));
			assocs.add(a);
		}
		
		out.openCloseField(f, fieldId, classId);
	}

	@Override
	public void visit(Method m) {
		operationId++;
		
		out.openMethod(m, operationId, classId);
		
		// return value - mock argumentu
		isReturn = true;
		Argument ret = new Argument("");
		ret.setType(m.getReturnedType());
		visit(ret);
		
		// argumenty
		isReturn = false;
		for(Argument arg : m.getArguments().values()){
			visit(arg);
		}
		
		out.closeMethod();
	}

	@Override
	public void visit(Package p) {
		packageName = p.getName();
		out.openPackage(packageName);
		
		// klasy
		for (components.Class c : p.getClasses()) {
			visit(c);
		}
		
		out.closePackage();
	}

	@Override
	public void visit(ClassDiagram cd) {
		// paczki
		this.packages = cd.getPackages();
		for (Package p : cd.getPackages()) {
			visit(p);
		}
		
		// dziedziczenie
		for (Generalisation f : gens) {
			out.openCloseGeneralisation(f);
		}

		// wyliczenia
		for (Association a : assocs) {
			out.openCloseAssociation(a);
		}

		// comit templete'a
		out.commit();
	}

	//- PRIVATE & PROTECTED METHODS -------------------------------------------------------------------------
	private boolean isSuchType(String name){
		for (Package p : packages) {
			if(p.getClass(name) != null)
				return true;				
		}
		return false;
	}
	
	private void handleInterfaces(Class c) {
		if(c.getInterfaces() != null){
			String[] name = c.getInterfaces().split(",");
			for (String n : name) {
				gens.add(new Generalisation(++genId , packageName, c.getName(), n.trim(), "Abstraction"));	
			}
		}
	}

	private void handleGeneralization(Class c) {
		if(c.getExtends() != null && isSuchType(c.getExtends())){
			gens.add(new Generalisation(++genId , packageName, c.getName(), c.getExtends(), "Generalization"));
		}
	}

	private void handleInnerClasses(Class c) {
		String upperPackage = packageName;
		if(c.getInnerClasses().size() > 0){
			out.openInnerClass();
			packageName += "." + c.getName();
			for(components.Class ic : c.getInnerClasses()){
				visit(ic);
			}
			
			out.closeInnerClass();
			packageName = upperPackage;
		}
	}
	
	//- GETTERS & SETTERS ----------------------------------------------------------------------------------------

}
